﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;

using LanguageDetector.Data;

namespace LanguageDetector
{
    public class TrainerLogic
    {
        #region vars & ctor
        ILog logger = LogManager.GetLogger(typeof(TrainerLogic));

        Func<object, string> _extractInput;
        public TrainerLogic(LanguageStat lang, Func<object, string> extractInput)
        {
            AutoSaveToDB = true; //by default
            if (lang == null)
                _errors.Add(new ArgumentNullException("LanguageStat input can not be null!"));
            else if (extractInput == null)
                _errors.Add(new ArgumentNullException("ExtractInput action can not be null!"));
            else
            {
                _errors.AddRange(LanguageStatDB.Sync(lang));
                if (lang.ID > 0)
                {
                    var grams = BiGramStatDB.Get(new BiGramCriteria());
                    Errors.AddRange(grams.Errors);
                    var bgMap = (from g in grams where g.LanguageID == lang.ID select g).ToDictionary(b => b.BiGram);
                    if (!bgMap.IsNullOrEmpty())
                        _bstats = bgMap;
                }
            }

            if (!_errors.IsNullOrEmpty())
            {
                _errors.ForEach(ex => logger.Error("Trainer CTOR error", ex));
                if (_errors.Count == 1)
                    throw (_errors.First());
            }
            else
            {
                _lang = lang;
                _extractInput = extractInput;
            }
        }

        #endregion

        public event Action<TrainerLogic, TrainerDoneEventArgs> Done = null;
        public void Run(object content)
        {
            var started = DateTime.Now;
            try
            {
                var s = FetchStringInput(content);
                s = s.Clean((_lang.IncludeASCII ? CleanLevel.None : CleanLevel.Alphabet) | CleanLevel.Digit | CleanLevel.Lower | CleanLevel.Marking | CleanLevel.Trim | CleanLevel.WhiteSpace | CleanLevel.Tag);
                if (!string.IsNullOrEmpty(s)) //learn here
                {
                    var words = s.Split(new [] {' '}, StringSplitOptions.RemoveEmptyEntries);
                    for (var i = 0; i < words.Length; i++)
                    {
                        var word = words[i];
                        for (int j = 1; j < word.Length; j++)
                        {
                            var bg = new BiGram(word[j - 1], word[j]);
                            if (_bstats.ContainsKey(bg))
                                _bstats[bg].Count++;
                            else
                                _bstats.Add(bg, new BiGramStat() { BiGram = bg, LanguageID = _lang.ID, Count=1, });
                            _lang.BiGramTotal++;
                        }
                    }
                    if (AutoSaveToDB) //perists to db
                        SaveResults();
                }
                if (_errors.Count > 0)
                    _errors.ForEach(e => logger.Error("Trainer run error.", e));
            }
            catch (Exception ex) 
            { 
                _errors.Add(ex);
                logger.Error("Training error!", ex);
            }
            finally
            {
                if (Done != null)
                    Done(this, new TrainerDoneEventArgs(content, LanguageStat, Result, Errors, DateTime.Now.Subtract(started)));
            }
        }

        public void SaveResults()
        {
            try
            {
                _errors.AddRange(LanguageStatDB.Save(new List<LanguageStat>() { _lang }));
                _errors.AddRange(BiGramStatDB.Save(_bstats.Values.ToList(), this.AutoSaveToDB));
            }
            catch (Exception ex)
            {
                _errors.Add(ex);
                logger.Error("Error during save", ex);
            }
        }

        string FetchStringInput(object content)
        {
            string s = null;
            if (content == null)
                _errors.Add(new ArgumentNullException("The provided content object is null!"));
            else
            {
                s = _extractInput(content);
                if (string.IsNullOrEmpty(s))
                    _errors.Add(new ArgumentOutOfRangeException("The provided content object contains no input string data!"));
            }
            return s;
        }

        #region public properties

        public bool AutoSaveToDB { get; set; }

        List<Exception> _errors = new List<Exception>();
        public List<Exception> Errors
        {
            get { return _errors; }
        }

        LanguageStat _lang;
        public LanguageStat LanguageStat
        {
            get { return _lang; }
        }

        Dictionary<BiGram, BiGramStat> _bstats = new Dictionary<BiGram, BiGramStat>();
        public Dictionary<BiGram, BiGramStat> Result
        {
            get { return _bstats; }
        }

        #endregion
    }
}
